commonlibsse_ng\re\b/
BSString.rs

1use core::marker::PhantomData;
2use core::{alloc::Layout, fmt};
3use std::ffi::{CStr, CString};
4
5use std_fork::alloc::SelflessAllocator;
6use stdx::ptr::Unique;
7
8use crate::re::MemoryManager::TESGlobalAlloc;
9
10// NOTE: The `SStaticStringT` is omitted because it is not used.
11
12/// `BSString` represents a string that stores its data as raw bytes from FFI with an undefined encoding.
13///
14/// It provides methods for managing the string's data and converting it to/from `CStr`.
15/// This struct is designed for situations where string encoding isn't guaranteed, such as when dealing with FFI.
16///
17/// # Encoding
18///
19/// It has been confirmed that this string is also UTF-8 when esp. etc. are saved in UTF-8.
20///
21/// # Examples
22///
23/// ```rust
24/// # use commonlibsse_ng::re::BSString::BSString;
25/// let mut bs = BSString::new();
26/// assert_eq!(bs.len(), 0);
27///
28/// bs.set_c_str(&c"Hello, Rust!");
29/// assert_eq!(bs.len(), 13);
30/// assert_eq!(bs.as_c_str().to_str(), Ok("Hello, Rust!"));
31/// ```
32#[repr(C)]
33pub struct BSString<A = TESGlobalAlloc>
34where
35    A: SelflessAllocator,
36{
37    /// data coming from ffi should be `*mut c_char` because it could be null
38    data: Option<Unique<u8>>,
39    /// The number of bytes that the string currently contains that are valid. (Including null-terminated characters.)
40    size: u16,
41    /// Number of bytes allocated by the allocator
42    capacity: u16,
43
44    pad0C: u32,
45
46    /// allocator API
47    alloc: PhantomData<A>,
48}
49const _: () = assert!(core::mem::size_of::<BSString>() == 0x10);
50
51#[derive(Copy, Clone, PartialEq, Eq, Debug)]
52pub enum BSStringError {
53    /// string is too long to fit in a u16
54    TooLong,
55    /// allocation failed
56    AllocFailed,
57    /// string contains interior null bytes
58    InteriorNul,
59}
60
61impl core::error::Error for BSStringError {}
62impl fmt::Display for BSStringError {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        match self {
65            Self::TooLong => f.write_str("string is too long. max is u16::MAX(65535) bytes"),
66            Self::AllocFailed => f.write_str("allocation failed"),
67            Self::InteriorNul => f.write_str("string contains interior null bytes"),
68        }
69    }
70}
71
72impl BSString {
73    /// Creates a new `BSString` instance with no data.
74    ///
75    /// # Examples
76    ///
77    /// ```rust
78    /// # use commonlibsse_ng::re::BSString::BSString;
79    /// let bs = BSString::new();
80    /// assert_eq!(bs.len(), 0);
81    /// ```
82    #[inline]
83    pub const fn new() -> Self {
84        Self::new_in()
85    }
86}
87
88impl<A> BSString<A>
89where
90    A: SelflessAllocator,
91{
92    /// Creates a new, empty `BSString`.
93    ///
94    /// # Examples
95    ///
96    /// ```rust
97    /// use commonlibsse_ng::re::BSString::BSString;
98    /// use stdx::alloc::Global;
99    ///
100    /// let bs = BSString::<Global>::new_in();
101    /// assert!(bs.is_empty());
102    /// ```
103    #[inline]
104    pub const fn new_in() -> Self {
105        Self { data: None, size: 0, capacity: 0, pad0C: 0, alloc: PhantomData }
106    }
107
108    /// Allocate a new `Self` from `&CStr` argument.
109    ///
110    /// # Examples
111    ///
112    /// ```rust
113    /// # use commonlibsse_ng::re::BSString::BSString;
114    /// use stdx::alloc::Global;
115    /// let bs = BSString::<Global>::from_c_str(c"Hello").unwrap();
116    /// assert_eq!(bs.as_c_str(), c"Hello");
117    /// ```
118    ///
119    /// # Errors
120    /// - If the string is too long to fit in a `u16`, or if allocation fails.
121    /// - If allocations fail.
122    pub fn from_c_str(s: &CStr) -> Result<Self, BSStringError> {
123        let mut string = Self::new_in();
124        string.set_c_str(s)?;
125        Ok(string)
126    }
127
128    /// Clears the string's data, resetting its size and capacity.
129    ///
130    /// # Examples
131    ///
132    /// ```rust
133    /// # use commonlibsse_ng::re::BSString::BSString;
134    /// let mut bs = BSString::new();
135    /// bs.set_c_str(&c"Hello, Rust!");
136    /// assert!(!bs.is_empty());
137    /// bs.clear();
138    /// assert!(bs.is_empty());
139    /// ```
140    #[inline]
141    pub fn clear(&mut self) {
142        if self.capacity == 0 {
143            return;
144        }
145
146        // Safety: avoid double free by `take`
147        if let Some(ptr) = self.data.take() {
148            unsafe { A::deallocate(ptr.as_non_null_ptr(), self.layout()) };
149        }
150        self.size = 0;
151        self.capacity = 0;
152    }
153
154    /// Sets the content of the `BSString` from a `CStr`.
155    ///
156    /// This method will overwrite the current data, resizing if necessary.
157    ///
158    /// # Examples
159    ///
160    /// ```rust
161    /// # use commonlibsse_ng::re::BSString::BSString;
162    /// let mut bs = BSString::new();
163    /// bs.set_c_str(&c"Hello, Rust!");
164    /// assert_eq!(bs.len(), 13);
165    /// ```
166    ///
167    /// # Errors
168    /// - If the string is too long to fit in a `u16`, or if allocation fails.
169    /// - If allocations fail.
170    pub fn set_c_str(&mut self, cstr: &CStr) -> Result<(), BSStringError> {
171        let bytes = cstr.to_bytes_with_nul();
172        let len = bytes.len();
173
174        if len == 0 {
175            self.clear();
176            return Ok(());
177        }
178        if len > (u16::MAX as usize) {
179            return Err(BSStringError::TooLong);
180        }
181
182        let len = len as u16;
183
184        let new_layout = Self::new_layout(len);
185
186        let mut reuse = false;
187        let new_ptr = unsafe {
188            match self.data {
189                Some(old_ptr) => {
190                    let old_layout = self.layout();
191                    if new_layout.size() > old_layout.size() {
192                        A::grow(old_ptr.as_non_null_ptr(), old_layout, new_layout)
193                            .map_err(|_| BSStringError::AllocFailed)?
194                            .cast()
195                    } else {
196                        reuse = true;
197                        old_ptr.as_non_null_ptr() // Current buffer is sufficient, so reuse as is
198                    }
199                }
200                None => A::allocate(new_layout).map_err(|_| BSStringError::AllocFailed)?.cast(),
201            }
202        };
203
204        unsafe { core::ptr::copy_nonoverlapping(bytes.as_ptr(), new_ptr.as_ptr(), len as usize) };
205
206        self.data = Some(Unique::from(new_ptr));
207        self.size = len;
208        if !reuse {
209            self.capacity = len;
210        }
211        Ok(())
212    }
213
214    /// Checks if the string is empty.
215    ///
216    /// # Examples
217    ///
218    /// ```rust
219    /// # use commonlibsse_ng::re::BSString::BSString;
220    /// let bs = BSString::new();
221    /// assert!(bs.is_empty());
222    /// ```
223    #[inline]
224    pub const fn is_empty(&self) -> bool {
225        self.size == 0
226    }
227
228    /// Returns the bytes length of the string.
229    ///
230    /// # Examples
231    ///
232    /// ```rust
233    /// # use commonlibsse_ng::re::BSString::BSString;
234    /// let mut bs = BSString::new();
235    /// bs.set_c_str(&c"Hello"); // Contains `\0`
236    /// assert_eq!(bs.len(), 6);
237    /// ```
238    #[inline]
239    pub const fn len(&self) -> usize {
240        self.size as usize
241    }
242
243    /// Returns the capacity of the string.
244    ///
245    /// # Examples
246    ///
247    /// ```rust
248    /// # use commonlibsse_ng::re::BSString::BSString;
249    /// let mut bs = BSString::new();
250    /// bs.set_c_str(&c"Hello");
251    /// assert!(bs.capacity() >= 5);
252    /// ```
253    #[inline]
254    pub const fn capacity(&self) -> u16 {
255        self.capacity
256    }
257
258    /// Returns the underlying bytes of the string, including the null terminator.
259    ///
260    /// # Examples
261    ///
262    /// ```rust
263    /// # use commonlibsse_ng::re::BSString::BSString;
264    /// let mut bs = BSString::new();
265    /// bs.set_c_str(&c"Hello");
266    /// assert_eq!(bs.as_bytes_with_null(), b"Hello\0");
267    /// ```
268    #[inline]
269    pub const fn as_bytes_with_null(&self) -> &[u8] {
270        if self.size == 0 {
271            return &[];
272        }
273
274        match self.data {
275            Some(ref data) => unsafe { core::slice::from_raw_parts(data.as_ptr(), self.len()) },
276            None => &[],
277        }
278    }
279
280    /// Returns the string as a `CStr`, which is suitable for FFI.
281    ///
282    /// # Examples
283    ///
284    /// ```rust
285    /// # use commonlibsse_ng::re::BSString::BSString;
286    /// let mut bs = BSString::new();
287    /// bs.set_c_str(&c"Hello");
288    /// assert_eq!(bs.as_c_str().to_str(), Ok("Hello"));
289    /// ```
290    #[inline]
291    pub const fn as_c_str(&self) -> &CStr {
292        if self.size == 0 {
293            return c"";
294        }
295        unsafe { CStr::from_bytes_with_nul_unchecked(self.as_bytes_with_null()) }
296    }
297
298    /// Gets a current layout self.
299    ///
300    /// # Panics
301    /// On arithmetic overflow or when the total size would exceed
302    /// `isize::MAX`, panic.
303    fn layout(&self) -> Layout {
304        Self::new_layout(self.capacity)
305    }
306
307    /// Creates a layout describing the record for a `[T; n]`.
308    ///
309    /// # Panics
310    /// On arithmetic overflow or when the total size would exceed
311    /// `isize::MAX`, panic.
312    fn new_layout(n: u16) -> Layout {
313        Layout::array::<u8>(n as usize).expect("BSTString need: alloc size < isize::MAX")
314    }
315}
316
317impl<A> Drop for BSString<A>
318where
319    A: SelflessAllocator,
320{
321    #[inline]
322    fn drop(&mut self) {
323        self.clear();
324    }
325}
326
327impl Default for BSString {
328    #[inline]
329    fn default() -> Self {
330        Self::new()
331    }
332}
333
334impl<A> fmt::Debug for BSString<A>
335where
336    A: SelflessAllocator,
337{
338    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339        f.debug_struct("BSString")
340            .field("data", &self.as_c_str())
341            .field("size", &self.size)
342            .field("capacity", &self.capacity)
343            .finish()
344    }
345}
346
347impl<A> Clone for BSString<A>
348where
349    A: SelflessAllocator,
350{
351    fn clone(&self) -> Self {
352        if self.size == 0 {
353            return Self::new_in();
354        }
355        let Some(data) = self.data else {
356            return Self::new_in();
357        };
358
359        let len = self.size;
360        let layout = Self::new_layout(len);
361
362        let ptr = unsafe {
363            let new_ptr = A::allocate(layout).expect("allocation failed in clone").cast();
364            core::ptr::copy_nonoverlapping(data.as_ptr(), new_ptr.as_ptr(), len as usize);
365            new_ptr
366        };
367
368        Self {
369            data: Some(unsafe { Unique::new_unchecked(ptr.as_ptr()) }),
370            size: len,
371            capacity: len,
372            pad0C: 0,
373            alloc: PhantomData,
374        }
375    }
376}
377
378impl<A, B> PartialEq<BSString<B>> for BSString<A>
379where
380    A: SelflessAllocator,
381    B: SelflessAllocator,
382{
383    fn eq(&self, other: &BSString<B>) -> bool {
384        self.as_c_str() == other.as_c_str()
385    }
386}
387
388impl<A> PartialEq<CStr> for BSString<A>
389where
390    A: SelflessAllocator,
391{
392    fn eq(&self, other: &CStr) -> bool {
393        self.as_c_str() == other
394    }
395}
396
397impl<A> Eq for BSString<A> where A: SelflessAllocator {}
398
399impl<A, B> PartialOrd<BSString<B>> for BSString<A>
400where
401    A: SelflessAllocator,
402    B: SelflessAllocator,
403{
404    fn partial_cmp(&self, other: &BSString<B>) -> Option<core::cmp::Ordering> {
405        self.as_c_str().partial_cmp(other.as_c_str())
406    }
407}
408
409impl<A> Ord for BSString<A>
410where
411    A: SelflessAllocator,
412{
413    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
414        self.as_c_str().cmp(other.as_c_str())
415    }
416}
417
418impl<A> core::hash::Hash for BSString<A>
419where
420    A: SelflessAllocator,
421{
422    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
423        self.as_c_str().hash(state);
424    }
425}
426
427////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
428
429impl<A: SelflessAllocator> TryFrom<CString> for BSString<A> {
430    type Error = BSStringError;
431
432    fn try_from(c_string: CString) -> Result<Self, Self::Error> {
433        let bytes = c_string.into_bytes_with_nul();
434        let len = bytes.len();
435
436        if len > u16::MAX as usize {
437            return Err(BSStringError::TooLong);
438        }
439
440        let len_u16 = len as u16;
441        let layout = Self::new_layout(len_u16);
442
443        let ptr = unsafe {
444            let ptr = A::allocate(layout).map_err(|_| BSStringError::AllocFailed)?.cast();
445            core::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.as_ptr(), len);
446            ptr
447        };
448
449        Ok(Self {
450            data: Some(Unique::from(ptr)),
451            size: len_u16,
452            capacity: len_u16,
453            pad0C: 0,
454            alloc: PhantomData,
455        })
456    }
457}
458
459impl<A: SelflessAllocator> TryFrom<&str> for BSString<A> {
460    type Error = BSStringError;
461
462    #[inline]
463    fn try_from(s: &str) -> Result<Self, Self::Error> {
464        let c_string = CString::new(s).map_err(|_| BSStringError::InteriorNul)?;
465        Self::try_from(c_string)
466    }
467}
468
469impl<A: SelflessAllocator> TryFrom<String> for BSString<A> {
470    type Error = BSStringError;
471
472    #[inline]
473    fn try_from(s: String) -> Result<Self, Self::Error> {
474        Self::try_from(s.as_str())
475    }
476}
477
478#[cfg(test)]
479mod tests {
480    use super::*;
481
482    type BSString = super::BSString<stdx::alloc::Global>;
483
484    #[test]
485    fn test_bs_string() {
486        let mut bs = BSString::new_in();
487
488        assert_eq!(bs.len(), 0);
489        assert_eq!(bs.as_c_str().to_str(), Ok(""));
490
491        let input = c"Hello, Rust!";
492        bs.set_c_str(input).unwrap();
493
494        assert_eq!(bs.len(), 13);
495        assert_eq!(bs.as_c_str().to_str(), Ok("Hello, Rust!"));
496
497        let input2 = c"Short";
498        bs.set_c_str(input2).unwrap();
499
500        assert_eq!(bs.len(), 6);
501        assert_eq!(bs.as_c_str().to_str(), Ok("Short"));
502    }
503
504    #[test]
505    fn test_too_long_cstr() {
506        let too_large_vec = {
507            let mut v = vec![b'a'; u16::MAX as usize + 1];
508            v.push(0); // null terminator
509            v
510        };
511
512        let too_large_c_string =
513            CString::from_vec_with_nul(too_large_vec).expect("CString too long for CStr");
514
515        let too_large_cstr = too_large_c_string.as_c_str();
516
517        let mut bs = BSString::new_in();
518        assert_eq!(bs.set_c_str(too_large_cstr), Err(BSStringError::TooLong));
519    }
520}